/*
 * Copyright (c) 2017-2019, Lindenis Tech. Ltd.
 * All rights reserved.
 *
 * File:
 *
 * Description:
 *
 * Author:
 *      xiaoshujun@lindeni.com
 *
 * Create Date:
 *      2019/08/08
 *
 * History:
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "osal_cond.h"
#include "osal_mutex.h"
#include "osal_queue.h"

#include "osal_log.h"

#ifndef MELIS_OS
int OSAL_QueueCreate(OSAL_QUEUE *queueHandle, int maxQueueElem)
{
    int i = 0;
    OSAL_QElem *newqelem = NULL;
    OSAL_QElem *currentqelem = NULL;
    OSAL_QUEUE *queue = (OSAL_QUEUE *)queueHandle;

    int ret = ErrorNone;

    if (!queue)
        return ErrorUnknown;

    ret = OSAL_MutexCreate(&queue->qMutex);
    if (ret != ErrorNone)
        return ret;

	queue->maxElem = maxQueueElem;

    queue->first = (OSAL_QElem *)malloc(sizeof(OSAL_QElem));
    if (queue->first == NULL)
        return ErrorUnknown;

    memset(queue->first, 0, sizeof(OSAL_QElem));
    currentqelem = queue->last = queue->first;
    queue->numElem = 0;

    for (i = 0; i < (queue->maxElem - 2); i++) {
        newqelem = (OSAL_QElem *)malloc(sizeof(OSAL_QElem));
        if (newqelem == NULL) {
            while (queue->first != NULL) {
                currentqelem = queue->first->qNext;
                free((void *)queue->first);
                queue->first = currentqelem;
            }
            return ErrorUnknown;
        } else {
            memset(newqelem, 0, sizeof(OSAL_QElem));
            currentqelem->qNext = newqelem;
            currentqelem = newqelem;
        }
    }

    currentqelem->qNext = queue->first;

    return ErrorNone;
}

int OSAL_QueueTerminate(OSAL_QUEUE *queueHandle)
{
    int i = 0;
    OSAL_QElem *currentqelem = NULL;
    OSAL_QUEUE *queue = (OSAL_QUEUE *)queueHandle;
    int ret = ErrorNone;

    if (!queue)
        return ErrorUnknown;

    for ( i = 0; i < (queue->maxElem - 2); i++) {
        currentqelem = queue->first->qNext;
        free(queue->first);
        queue->first = currentqelem;
    }

    if(queue->first) {
        free(queue->first);
        queue->first = NULL;
    }

    ret = OSAL_MutexTerminate(queue->qMutex);

    return ret;
}

int OSAL_Queue(OSAL_QUEUE *queueHandle, void *data)
{
    OSAL_QUEUE *queue = (OSAL_QUEUE *)queueHandle;
    if (queue == NULL)
        return -1;

    OSAL_MutexLock(queue->qMutex);

    if ((queue->last->data != NULL) || (queue->numElem >= queue->maxElem)) {
        OSAL_MutexUnlock(queue->qMutex);
        return -1;
    }
    queue->last->data = data;
    queue->last = queue->last->qNext;
    queue->numElem++;

    OSAL_MutexUnlock(queue->qMutex);
    return 0;
}

void *OSAL_Dequeue(OSAL_QUEUE *queueHandle)
{
    void *data = NULL;
    OSAL_QUEUE *queue = (OSAL_QUEUE *)queueHandle;
    if (queue == NULL)
        return NULL;

    OSAL_MutexLock(queue->qMutex);

    if ((queue->first->data == NULL) || (queue->numElem <= 0)) {
        OSAL_MutexUnlock(queue->qMutex);
        return NULL;
    }
    data = queue->first->data;
    queue->first->data = NULL;
    queue->first = queue->first->qNext;
    queue->numElem--;

    OSAL_MutexUnlock(queue->qMutex);
    return data;
}

int OSAL_GetElemNum(OSAL_QUEUE *queueHandle)
{
    int ElemNum = 0;
    OSAL_QUEUE *queue = (OSAL_QUEUE *)queueHandle;
    if (queue == NULL)
        return -1;

    OSAL_MutexLock(queue->qMutex);
    ElemNum = queue->numElem;
    OSAL_MutexUnlock(queue->qMutex);
    return ElemNum;
}

int OSAL_SetElemNum(OSAL_QUEUE *queueHandle, int ElemNum)
{
    OSAL_QUEUE *queue = (OSAL_QUEUE *)queueHandle;
    if (queue == NULL)
        return -1;

    OSAL_MutexLock(queue->qMutex);
    queue->numElem = ElemNum; 
    OSAL_MutexUnlock(queue->qMutex);
    return ElemNum;
}

int OSAL_QueueSetElem(OSAL_QUEUE *queueHandle, void *data)
{
	int i = 0;
    OSAL_QUEUE *queue = (OSAL_QUEUE *)queueHandle;
    if (queue == NULL)
        return -1;

    OSAL_MutexLock(queue->qMutex);
	
    if ((queue->last->data != NULL) || (queue->numElem >= queue->maxElem)) {
        OSAL_MutexUnlock(queue->qMutex);
        return -1;
    }

	for (i = 0; i < queue->numElem; i++)
	{
		if ((queue->first->data == data)
			|| (queue->last->data == data))
		{
			// if there is an same elem, do not in queue anyway
			OSAL_MutexUnlock(queue->qMutex);
        	return 0;
		}
	}
	
    queue->last->data = data;
    queue->last = queue->last->qNext;
    queue->numElem++;

    OSAL_MutexUnlock(queue->qMutex);
    return 0;
}
#endif // MELIS_OS

#define DEFAULT_MAX_NB      1024
typedef struct _osal_elem
{
    void                * obj;
    struct _osal_elem   * next;
} osal_elem;

typedef struct _queue_context_t
{
    char                name[256];
    osal_elem *         first;
    osal_elem *         last;
    int                 i_num_elem;
	int                 i_max_elem;
    OSAL_mutex *        mutex;
    OSAL_cond *         cond;
    int                 i_abort;
    int                 i_encnt;
    int                 i_decnt;
} queue_ctx_t;

_handle_t osal_queue_create(int max, char * queue_name)
{
    queue_ctx_t * p_ctx = malloc(sizeof(queue_ctx_t));
    if (p_ctx == NULL)
    {
        return (_handle_t)NULL;
    }

    memset(p_ctx, 0, sizeof(queue_ctx_t));

    if (max > 0) {
        p_ctx->i_max_elem = max;
    } else {
        p_ctx->i_max_elem = DEFAULT_MAX_NB;
    }

    if (queue_name) {
        strcpy(p_ctx->name, queue_name);
    }

    p_ctx->mutex = OSAL_CreateMutex();
    if (!p_ctx->mutex)
    {
        return (_handle_t)NULL;
    }

    p_ctx->cond = OSAL_CreateCond();
    if (!p_ctx->cond)
    {
        return (_handle_t)NULL;
    }

    return (_handle_t)p_ctx;
}

void osal_queue_destroy(_handle_t h_queue)
{
    queue_ctx_t * p_ctx = (queue_ctx_t *)h_queue;
    if (p_ctx != NULL)
    {
        OSAL_DestroyCond(p_ctx->cond);
        OSAL_DestroyMutex(p_ctx->mutex);
        free(p_ctx);
        p_ctx = NULL;
    }
}

int osal_enqueue(_handle_t h_queue, void * obj, int block)
{
    queue_ctx_t * p_ctx = (queue_ctx_t *)h_queue;
    osal_elem * p_elem = NULL;
    int ret = -1;

    if (p_ctx == NULL || obj == NULL)
    {
        loge("error input parameters");
        return -1;
    }

    OSAL_LockMutex(p_ctx->mutex);
    while(!p_ctx->i_abort)
    {
        if (p_ctx->i_num_elem >= p_ctx->i_max_elem)
        {
            if (block) {
                OSAL_CondWait(p_ctx->cond, p_ctx->mutex);
                continue;
            } else {
                ret = -1;
                goto _exit_;
            }
        }
        p_elem = (osal_elem *)malloc(sizeof(osal_elem));
        if (!p_elem) {
            ret = -1;
            goto _exit_;
        }

        p_elem->obj = obj;
        p_elem->next = NULL;

        if (!p_ctx->first || !p_ctx->last) { // queue is empty
            p_ctx->first = p_elem;
            p_ctx->last = p_elem;
        } else {
            p_ctx->last->next = p_elem;
        }
        p_ctx->last = p_elem;
        p_ctx->i_num_elem++;

        logv("enqueue %s, cnt: %d", p_ctx->name, p_ctx->i_encnt++);

        ret = 0;
        break;
    }

_exit_:
    logv("%s, enqueue elem count: %d, first: %p", p_ctx->name, p_ctx->i_num_elem, p_ctx->first);
    OSAL_CondSignal(p_ctx->cond);
    OSAL_UnlockMutex(p_ctx->mutex);

    return ret;
}

int osal_dequeue_timeout(_handle_t h_queue, void ** obj, int timeout_ms)
{
    queue_ctx_t * p_ctx = (queue_ctx_t *)h_queue;
    osal_elem * p_elem = NULL;
    int ret = -1;

    if (p_ctx == NULL || obj == NULL)
    {
        loge("error input parameters");
        return -1;
    }

    OSAL_LockMutex(p_ctx->mutex);
    while(!p_ctx->i_abort)
    {
        p_elem = p_ctx->first;
        if (p_elem)
        {
            p_ctx->first = p_ctx->first->next;
            p_ctx->i_num_elem--;
            *obj = p_elem->obj;
            free(p_elem);
            ret = 0;
            logv("dequeue %s, cnt: %d", p_ctx->name, p_ctx->i_decnt++);
            goto _exit_;
        }

        if (timeout_ms == 0)
        {
            // no-block
            ret = -1;
            goto _exit_;
        }
        else if(timeout_ms < 0)
        {
            // block
            OSAL_CondWait(p_ctx->cond, p_ctx->mutex);
            continue;
        }
        else if(timeout_ms > 0)
        {
            ret = OSAL_CondWaitTimeout(p_ctx->cond, p_ctx->mutex, timeout_ms);
            if (ret == 0 && p_ctx->i_abort == 0)
            {
                continue;
            }
            else
            {
                ret = -1;
                goto _exit_;
            }
        }
    }

_exit_:
    logv("%s, dequeue elem count: %d", p_ctx->name, p_ctx->i_num_elem);
    OSAL_CondSignal(p_ctx->cond);
    OSAL_UnlockMutex(p_ctx->mutex);

    return ret;
}

int osal_dequeue(_handle_t h_queue, void ** obj, int block)
{
    int timeout_ms = (block == QUEUE_BLOCK) ? -1 : 0;
    return osal_dequeue_timeout(h_queue, obj, timeout_ms);
}

int osal_queue_get_nb(_handle_t h_queue)
{
    queue_ctx_t * p_ctx = (queue_ctx_t *)h_queue;
    int nb;

    if (p_ctx == NULL)
    {
        loge("error input parameters");
        return -1;
    }

    OSAL_LockMutex(p_ctx->mutex);
    nb = p_ctx->i_num_elem;
    OSAL_UnlockMutex(p_ctx->mutex);

    return nb;
}

void osal_queue_flush(_handle_t h_queue, int free_obj)
{
    queue_ctx_t * p_ctx = (queue_ctx_t *)h_queue;
    osal_elem * p_elem = NULL;

    OSAL_LockMutex(p_ctx->mutex);
    p_elem = p_ctx->first;
    while(p_elem)
    {
        p_ctx->first = p_ctx->first->next;
        p_ctx->i_num_elem--;
        if (free_obj)
        {
            free(p_elem->obj);
        }
        free(p_elem);

        p_elem = p_ctx->first;
    }
    OSAL_UnlockMutex(p_ctx->mutex);
}

void osal_queue_flush2(_handle_t h_queue, f_free_obj free_obj)
{
    queue_ctx_t * p_ctx = (queue_ctx_t *)h_queue;
    osal_elem * p_elem = NULL;

    OSAL_LockMutex(p_ctx->mutex);
    p_elem = p_ctx->first;
    while(p_elem)
    {
        p_ctx->first = p_ctx->first->next;
        p_ctx->i_num_elem--;
        if (free_obj)
        {
            free_obj(p_elem->obj);
        }
        free(p_elem);

        p_elem = p_ctx->first;
    }
    OSAL_UnlockMutex(p_ctx->mutex);
}

void osal_queue_abort(_handle_t h_queue)
{
    queue_ctx_t * p_ctx = (queue_ctx_t *)h_queue;

    if (p_ctx == NULL)
    {
        loge("error input parameters");
        return;
    }

    OSAL_LockMutex(p_ctx->mutex);
    p_ctx->i_abort = 1;
    OSAL_CondSignal(p_ctx->cond);
    OSAL_UnlockMutex(p_ctx->mutex);
}

int osal_queue_head(_handle_t h_queue, void ** obj)
{
    queue_ctx_t * p_ctx = (queue_ctx_t *)h_queue;
    osal_elem * p_elem = NULL;
    int ret = -1;

    if (p_ctx == NULL || obj == NULL)
    {
        loge("error input parameters");
        return -1;
    }

    OSAL_LockMutex(p_ctx->mutex);
    p_elem = p_ctx->first;
    if (p_elem && p_elem->obj)
    {
        *obj = p_elem->obj;
        OSAL_UnlockMutex(p_ctx->mutex);
        return 0;
    }
    else
    {
        OSAL_UnlockMutex(p_ctx->mutex);
        return -1;
    }
}

